package splash

import (
	"context"
	"errors"
	"flag"
	"fmt"
	"time"

	"gitee.com/baixudong/gospider/browser"
	"gitee.com/baixudong/gospider/thread"
	"gitee.com/baixudong/gospider/tools"
	"github.com/gin-gonic/gin"
)

type Client struct {
	Host       string
	Port       int
	Proxy      string
	Headless   bool
	tasks      chan *Task
	ctx        context.Context
	pageNum    int
	browserNum int
}
type ClientOption struct {
	BrowserNum int
	PageNum    int
}

func NewFlagClient(ctx context.Context) *Client {
	browserNum := flag.Int("browserNum", 1, "浏览器的数量")
	pageNum := flag.Int("pageNum", 100, "标签页最大打开数量")
	host := flag.String("host", "", "host")
	port := flag.Int("port", 8200, "port")
	proxy := flag.String("proxy", "", "代理")
	headless := flag.Bool("headless", true, "是否开启无头模式")
	flag.Parse()
	cli := NewClient(ctx, ClientOption{
		BrowserNum: *browserNum,
		PageNum:    *pageNum,
	})
	cli.Headless = *headless
	cli.Host = *host
	cli.Port = *port
	cli.Proxy = *proxy
	return cli
}
func NewClient(ctx context.Context, options ...ClientOption) *Client {
	if ctx == nil {
		ctx = context.TODO()
	}
	var option ClientOption
	if len(options) > 0 {
		option = options[0]
	}
	if option.BrowserNum < 1 {
		option.BrowserNum = 1
	}
	if option.PageNum < 1 {
		option.PageNum = 100
	}
	return &Client{
		tasks:      make(chan *Task, option.BrowserNum*option.PageNum),
		ctx:        ctx,
		pageNum:    option.PageNum,
		browserNum: option.BrowserNum,
	}
}

type Task struct {
	rendOption RendOption
	Task       *thread.Task
	ctx        context.Context
	cnl        context.CancelFunc
	err        error
}

func (obj *Task) Result(preCtx context.Context) (Result, error) {
	var result Result
	select {
	case <-preCtx.Done():
		return result, preCtx.Err()
	case <-obj.Task.Done:
		if obj.Task.Error != nil {
			return result, obj.Task.Error
		}
		results := obj.Task.Result
		if len(results) != 2 {
			return result, errors.New("not found result")
		}
		if results[1] != nil {
			return result, results[1].(error)
		}
		result, ok := results[0].(Result)
		if !ok {
			return result, errors.New("parse result panic")
		}
		return result, nil
	}
}
func (obj *Client) Write(preCtx context.Context, option RendOption) (*Task, error) {
	task := new(Task)
	task.rendOption = option
	task.ctx, task.cnl = context.WithCancel(preCtx)
	obj.tasks <- task
	select {
	case <-task.ctx.Done():
		return task, task.err
	case <-time.After(time.Second * 60):
		return task, errors.New("time out")
	}
}

func (obj *Client) server() error {
	cli := gin.Default()
	cli.POST("/render", func(ctx *gin.Context) {
		var option RendOption
		result := make(map[string]any)
		err := ctx.BindJSON(&option)
		if err != nil {
			result["ok"] = false
			result["msg"] = fmt.Sprintf("%s:%s", "解析json error", err.Error())
			ctx.JSON(400, result)
			return
		}
		task, err := obj.Write(ctx.Copy(), option)
		if err != nil {
			result["ok"] = false
			result["msg"] = fmt.Sprintf("%s:%s", "create task error", err.Error())
			ctx.JSON(400, result)
			return
		}
		rs, err := task.Result(ctx.Copy())
		if err != nil {
			result["ok"] = false
			result["msg"] = fmt.Sprintf("%s:%s", "get result error", err.Error())
			ctx.JSON(400, result)
			return
		}
		result["ok"] = true
		result["result"] = rs
		ctx.JSON(200, result)
	})
	if obj.Port == 0 {
		var err error
		obj.Port, err = tools.FreePort()
		if err != nil {
			return err
		}
	}
	if obj.Host == "" {
		obj.Host = "0.0.0.0"
	}
	return cli.Run(fmt.Sprintf("%s:%d", obj.Host, obj.Port))
}
func (obj *Client) Run() error {
	for i := 0; i < obj.browserNum; i++ {
		brow, err := browser.NewClient(obj.ctx, browser.ClientOption{Headless: obj.Headless})
		if err != nil {
			return err
		}
		go obj.run(brow)
	}
	return obj.server()
}

type RendOption struct {
	Url           string           `json:"url"`
	Proxy         string           `json:"proxy"`
	SleepTime     int64            `json:"sleepTime"`
	WaitSelector  string           `json:"waitSelector"`
	Eval          string           `json:"eval"`
	EvalParams    map[string]any   `json:"evalParams"`
	Cookies       []browser.Cookie `json:"cookies"`
	ReturnCookies bool             `json:"returnCookies"` //是否返回cookies
	DisContent    bool             `json:"disContent"`    //禁止返回内容
}
type Result struct {
	Content    string           `json:"content"`
	EvalResult any              `json:"evalResult"`
	Cookies    []browser.Cookie `json:"cookies"`
}

func taskMain(ctx context.Context, brow *browser.Client, rendOption RendOption) (Result, error) {
	var result Result
	var err error
	page, err := brow.NewPage(ctx)
	if err != nil {
		return result, err
	}
	defer page.Close(ctx)
	if rendOption.Proxy != "" {
		page.ReqCli.Proxy = rendOption.Proxy
	}
	if rendOption.Cookies != nil {
		for i := 0; i < len(rendOption.Cookies); i++ {
			if rendOption.Cookies[i].Domain == "" && rendOption.Cookies[i].Url == "" {
				rendOption.Cookies[i].Url = rendOption.Url
			}
		}
		if err = page.SetCookies(ctx, rendOption.Cookies...); err != nil {
			return result, err
		}
	}
	if err = page.GoTo(ctx, rendOption.Url); err != nil {
		return result, err
	}
	if rendOption.WaitSelector != "" {
		if _, err = page.WaitSelector(ctx, rendOption.WaitSelector); err != nil {
			return result, err
		}
	}
	if rendOption.Eval != "" {
		rs, err := page.Eval(ctx, rendOption.Eval, rendOption.EvalParams)
		if err != nil {
			return result, err
		}
		result.EvalResult = rs.Value()
	}
	if rendOption.SleepTime > 0 {
		select {
		case <-ctx.Done():
			return result, ctx.Err()
		case <-time.After(time.Second * time.Duration(rendOption.SleepTime)):
		}
	}
	if rendOption.ReturnCookies {
		result.Cookies, err = page.GetCookies(ctx, rendOption.Url)
		if err != nil {
			return result, err
		}
	}
	if !rendOption.DisContent {
		html, err := page.Html(ctx)
		if err != nil {
			return result, err
		}
		result.Content = html.Html()
	}
	return result, nil
}
func (obj *Client) run(brow *browser.Client) {
	pool := thread.NewClient(obj.ctx, obj.pageNum)
	defer pool.Close()
	for {
		select {
		case <-obj.ctx.Done():
			return
		case task := <-obj.tasks:
			task.Task, task.err = pool.Write(&thread.Task{
				Func: taskMain,
				Args: []any{brow, task.rendOption},
			})
			task.cnl()
			if task.err != nil {
				return
			}
		}
	}
}
